package cn.sytton.taffe.framework.security.jwt;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.sytton.taffe.framework.factory.RedisUtil;
import cn.sytton.taffe.framework.constants.Constants;
import cn.sytton.taffe.framework.constants.RedisKey;
import cn.sytton.taffe.framework.properties.auth.AuthProperties;
import cn.sytton.taffe.framework.security.entity.LoginUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;

/**
 * jwt工厂
 *
 * @author skyrock
 */
@Slf4j
@Component
public class JwtFactory {

    @Resource
    private AuthProperties authProperties;

    private static final long MILLIS_SECOND = 1000L;  // 一秒
    private static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; // 一分
    private static final long MILLIS_MINUTE_FIFTEEN = 15 * MILLIS_MINUTE; // 15分

    @Resource
    private RedisUtil redisUtil;

    /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    public LoginUser getLoginUser(HttpServletRequest request) {
        // 获取请求携带的令牌
        String token = getToken(request);
        if (StrUtil.isNotEmpty(token)) {
            try {
                Claims claims = parseToken(token);
                // 解析对应的权限以及用户信息
                String tokenIdKey = getTokenIdKey(claims.getSubject());
                return redisUtil.getObject(tokenIdKey, LoginUser.class);
            }
            catch (Exception e) {
                log.error("获取token失败:" + e.getMessage(), e);
            }
        }
        return null;
    }

    /**
     * 设置用户身份信息
     */
    public void refreshLoginUser(LoginUser loginUser) {
        if (loginUser != null && StrUtil.isNotEmpty(loginUser.getTokenId())) {
            refreshToken(loginUser);
        }
    }

    /**
     * 删除用户身份信息
     */
    public void delLoginUser(String tokenId) {
        if (StrUtil.isNotEmpty(tokenId)) {
            String userKey = getTokenIdKey(tokenId);
            redisUtil.deleteObject(userKey);
        }
    }

    /**
     * 创建令牌
     *
     * @param loginUser 用户信息
     * @return 令牌
     */
    public String createToken(LoginUser loginUser) {
        String tokenId = IdUtil.simpleUUID();
        loginUser.setTokenId(tokenId);
        refreshToken(loginUser);

        return createToken(tokenId);
    }

    /**
     * 延长令牌有效期，相差不足15分钟，自动刷新缓存
     *
     * @param loginUser 用户
     */
    public void extendTime(LoginUser loginUser) {
        long expireTime = loginUser.getTokenExpireTimeStamp();
        long currentTime = System.currentTimeMillis();
        if (expireTime - currentTime <= MILLIS_MINUTE_FIFTEEN) {
            refreshToken(loginUser);
        }
    }

    /**
     * 刷新令牌有效期
     *
     * @param loginUser 登录信息
     */
    public void refreshToken(LoginUser loginUser) {
        loginUser.setTokenRefreshTimeStamp(System.currentTimeMillis());
        loginUser.setTokenExpireTimeStamp(loginUser.getTokenRefreshTimeStamp() + authProperties.getToken().getExpireTime() * MILLIS_MINUTE);
        String userKey = getTokenIdKey(loginUser.getTokenId());
        redisUtil.setObject(userKey, loginUser, authProperties.getToken().getExpireTime(), TimeUnit.MINUTES);
    }

    /**
     * 从数据声明生成令牌
     *
     * @param tokenId 用户tokenId
     * @return 令牌
     */
    private String createToken(String tokenId) {
        return Jwts.builder()
                .setSubject(tokenId)
                .signWith(SignatureAlgorithm.HS512, authProperties.getToken().getSecret()).compact();
    }

    /**
     * 从令牌中获取数据声明
     *
     * @param token 令牌
     * @return 数据声明
     */
    private Claims parseToken(String token) {
        return Jwts.parser()
                .setSigningKey(authProperties.getToken().getSecret())
                .parseClaimsJws(token)
                .getBody();
    }

    /**
     * 获取请求token
     *
     * @param request HttpServletRequest
     * @return token
     */
    private String getToken(HttpServletRequest request) {
        String token = request.getHeader(authProperties.getToken().getHeader());
        if (StrUtil.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) {
            token = token.replace(Constants.TOKEN_PREFIX, "");
        }
        return token;
    }

    private String getTokenIdKey(String tokenId) {
        return RedisKey.LOGIN_TOKEN_ID_KEY + tokenId;
    }

}
